<?php
/*
 * @Author: 张文Uncle
 * @Email: 861182774@qq.com
 * @Date: 2019-09-29 08:55:00
 * @LastEditors: 张文Uncle
 * @LastEditTime: 2019-09-29 14:03:12
 * @Descripttion:这是一个图片压缩的类，主要是把大图压缩成小图
 * 使用方法:
 require("ImageCompress.class.php");
//直接在浏览器上输出
(new \ImageCompress('IMG_20200924_072806.jpg'))->compressImg( );
//存在服务器硬盘上
//(new \ImageCompress('IMG_20200924_072806.jpg'))->compressImg( './upload/2.jpg');
 */

/**
 * 图片压缩类：通过缩放来压缩。
 * 如果要保持源图比例，把参数$percent保持为1即可。
 * 即使原比例压缩，也可大幅度缩小。数码相机4M图片。也可以缩为700KB左右。如果缩小比例，则体积会更小。
 *
 * 结果：可保存、可直接显示。
 */
class ImageCompress {

    private $src;
    private $image;
    private $imageinfo;
    private $percent;

    /**
     * 图片压缩
     * @param $src 源图
     * @param float $percent  压缩比例
     */
    public function __construct($src, $percent = 0.5) {
        $this->src = $src;
        $this->percent = $percent;
    }

    /** 高清压缩图片
     * @param string $saveName  提供图片名（可不带扩展名，用源图扩展名）用于保存。或不提供文件名直接显示
     */
    public function compressImg($saveName = '') {
        list($this->imageinfo['width'], $this->imageinfo['height'], $this->imageinfo['type'], $this->imageinfo['attr']) = getimagesize($this->src);
        $this->imageinfo['type'] = image_type_to_extension($this->imageinfo['type'], false);
        if ($this->imageinfo['type'] === 'gif') {
            $this->_openGIFImage();
        } else {
            $this->_openImage();
        }
        if (!empty($saveName)) {
            //保存
            $this->_saveImage($saveName);
        } else {
            $this->_showImage();
        }

    }

    /**
     * 内部：打开图片
     */
    private function _openImage() {
        $fun = "imagecreatefrom" . $this->imageinfo['type'];
        $this->image = $fun($this->src);
        $this->_thumpImage();
    }
    private function _openGIFImage() {
        $gfe = new \GifFrameExtractor();
        $gfe->extract($this->src);
        $frameImages = $gfe->getFrameImages(); //每一帧资源集
        $frameDurations = $gfe->getFrameDurations(); //每一帧持续时间
        foreach ($frameImages as $key => $im) {
            $this->image = $im;
            $this->_thumpImage();
            ob_start();
            imagegif($this->image);
            $rec[] = ob_get_contents();
            ob_clean();
        }
        $gif = new \GIFEncoder($rec, $frameDurations, 0, 2, 0, 0, 0, "bin");
        $this->image = $gif->GetAnimation();
    }

    /**
     * 内部：操作图片
     */
    private function _thumpImage() {
        $new_width = $this->imageinfo['width'] * $this->percent;
        $new_height = $this->imageinfo['height'] * $this->percent;
        $image_thump = imagecreatetruecolor($new_width, $new_height);
        if (in_array($this->imageinfo['type'], ['png', 'gif'])) {
            imagealphablending($image_thump, false);
            imagesavealpha($image_thump, true);
        }
        //将原图复制带图片载体上面，并且按照一定比例压缩,极大的保持了清晰度
        imagecopyresampled($image_thump, $this->image, 0, 0, 0, 0, $new_width, $new_height, $this->imageinfo['width'], $this->imageinfo['height']);
        imagedestroy($this->image);
        $this->image = $image_thump;
    }
    /**
     * 输出图片:保存图片则用saveImage()
     */
    private function _showImage() {
        header('Content-Type: image/' . $this->imageinfo['type']);
        if (is_string($this->image)) {
            echo $this->image;
        } else {
            $funcs = "image" . $this->imageinfo['type'];
            $funcs($this->image);
        }

    }
    /**
     * 保存图片到硬盘：
     * @param  string $dstImgName  1、可指定字符串不带后缀的名称，使用源图扩展名 。2、直接指定目标图片名带扩展名。
     */
    private function _saveImage($dstImgName) {
        if (empty($dstImgName)) {
            return false;
        }
        $allowImgs = ['.jpg', '.jpeg', '.png', '.bmp', '.wbmp', '.gif']; //如果目标图片名有后缀就用目标图片扩展名 后缀，如果没有，则用源图的扩展名
        $dstExt = strrchr($dstImgName, ".");
        $sourseExt = strrchr($this->src, ".");
        if (!empty($dstExt)) {
            $dstExt = strtolower($dstExt);
        }

        if (!empty($sourseExt)) {
            $sourseExt = strtolower($sourseExt);
        }

        //有指定目标名扩展名
        if (!empty($dstExt) && in_array($dstExt, $allowImgs)) {
            $dstName = $dstImgName;
        } elseif (!empty($sourseExt) && in_array($sourseExt, $allowImgs)) {
            $dstName = $dstImgName . $sourseExt;
        } else {
            $dstName = $dstImgName . $this->imageinfo['type'];
        }
        if (is_string($this->image)) {
            $hd = fopen($dstName, 'a');
            fwrite($hd, $this->image);
            fclose($hd);
        } else {
            $funcs = "image" . $this->imageinfo['type'];
            $funcs($this->image, $dstName);
        }
    }

    /**
     * 销毁图片
     */
    public function __destruct() {
        imagedestroy($this->image);
    }

}

Class GIFEncoder {
    var $GIF = "GIF89a"; /* GIF header 6 bytes        */
    var $VER = "GIFEncoder V2.06"; /* Encoder version                */

    var $BUF = Array();
    var $LOP = 0;
    var $DIS = 2;
    var $COL = -1;
    var $IMG = -1;

    var $ERR = Array(
        'ERR00' => "Does not supported function for only one image!",
        'ERR01' => "Source is not a GIF image!",
        'ERR02' => "Unintelligible flag ",
        'ERR03' => "Could not make animation from animated GIF source",
    );

    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GIFEncoder...
    ::
     */
    function __construct(
        $GIF_src, $GIF_dly, $GIF_lop, $GIF_dis,
        $GIF_red, $GIF_grn, $GIF_blu, $GIF_mod
    ) {
        if (!is_array($GIF_src) && !is_array($GIF_tim)) {
            printf("%s: %s", $this->VER, $this->ERR['ERR00']);
            exit(0);
        }
        $this->LOP = ($GIF_lop > -1) ? $GIF_lop : 0;
        $this->DIS = ($GIF_dis > -1) ? (($GIF_dis < 3) ? $GIF_dis : 3) : 2;
        $this->COL = ($GIF_red > -1 && $GIF_grn > -1 && $GIF_blu > -1) ?
            ($GIF_red | ($GIF_grn << 8) | ($GIF_blu << 16)) : -1;

        for ($i = 0; $i < count($GIF_src); $i++) {
            if (strToLower($GIF_mod) == "url") {
                $this->BUF[] = fread(fopen($GIF_src[$i], "rb"), filesize($GIF_src[$i]));
            } else if (strToLower($GIF_mod) == "bin") {
                $this->BUF[] = $GIF_src[$i];
            } else {
                printf("%s: %s ( %s )!", $this->VER, $this->ERR['ERR02'], $GIF_mod);
                exit(0);
            }
            if (substr($this->BUF[$i], 0, 6) != "GIF87a" && substr($this->BUF[$i], 0, 6) != "GIF89a") {
                printf("%s: %d %s", $this->VER, $i, $this->ERR['ERR01']);
                exit(0);
            }
            for ($j = (13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07))), $k = TRUE; $k; $j++) {
                switch ($this->BUF[$i]{ $j}) {
                    case "!":
                        if ((substr($this->BUF[$i], ($j + 3), 8)) == "NETSCAPE") {
                            printf("%s: %s ( %s source )!", $this->VER, $this->ERR['ERR03'], ($i + 1));
                            exit(0);
                        }
                        break;
                    case ";":
                        $k = FALSE;
                        break;
                }
            }
        }
        GIFEncoder::GIFAddHeader();
        for ($i = 0; $i < count($this->BUF); $i++) {
            GIFEncoder::GIFAddFrames($i, $GIF_dly[$i]);
        }
        GIFEncoder::GIFAddFooter();
    }
    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GIFAddHeader...
    ::
     */
    function GIFAddHeader() {
        $cmap = 0;

        if (ord($this->BUF[0]{10}) & 0x80) {
            $cmap = 3 * (2 << (ord($this->BUF[0]{10}) & 0x07));

            $this->GIF .= substr($this->BUF[0], 6, 7);
            $this->GIF .= substr($this->BUF[0], 13, $cmap);
            $this->GIF .= "!\377\13NETSCAPE2.0\3\1" . GIFEncoder::GIFWord($this->LOP) . "\0";
        }
    }
    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GIFAddFrames...
    ::
     */
    function GIFAddFrames($i, $d) {

        $Locals_str = 13 + 3 * (2 << (ord($this->BUF[$i]{10}) & 0x07));

        $Locals_end = strlen($this->BUF[$i]) - $Locals_str - 1;
        $Locals_tmp = substr($this->BUF[$i], $Locals_str, $Locals_end);

        $Global_len = 2 << (ord($this->BUF[0]{10}) & 0x07);
        $Locals_len = 2 << (ord($this->BUF[$i]{10}) & 0x07);

        $Global_rgb = substr($this->BUF[0], 13,
            3 * (2 << (ord($this->BUF[0]{10}) & 0x07)));
        $Locals_rgb = substr($this->BUF[$i], 13,
            3 * (2 << (ord($this->BUF[$i]{10}) & 0x07)));

        $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 0) .
            chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . "\x0\x0";

        if ($this->COL > -1 && ord($this->BUF[$i]{10}) & 0x80) {
            for ($j = 0; $j < (2 << (ord($this->BUF[$i]{10}) & 0x07)); $j++) {
                if (
                    ord($Locals_rgb{3 * $j + 0}) == ($this->COL >> 0) & 0xFF &&
                    ord($Locals_rgb{3 * $j + 1}) == ($this->COL >> 8) & 0xFF &&
                    ord($Locals_rgb{3 * $j + 2}) == ($this->COL >> 16) & 0xFF
                ) {
                    $Locals_ext = "!\xF9\x04" . chr(($this->DIS << 2) + 1) .
                        chr(($d >> 0) & 0xFF) . chr(($d >> 8) & 0xFF) . chr($j) . "\x0";
                    break;
                }
            }
        }
        switch ($Locals_tmp{0}) {
            case "!":
                $Locals_img = substr($Locals_tmp, 8, 10);
                $Locals_tmp = substr($Locals_tmp, 18, strlen($Locals_tmp) - 18);
                break;
            case ",":
                $Locals_img = substr($Locals_tmp, 0, 10);
                $Locals_tmp = substr($Locals_tmp, 10, strlen($Locals_tmp) - 10);
                break;
        }
        if (ord($this->BUF[$i]{10}) & 0x80 && $this->IMG > -1) {
            if ($Global_len == $Locals_len) {
                if (GIFEncoder::GIFBlockCompare($Global_rgb, $Locals_rgb, $Global_len)) {
                    $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
                } else {
                    $byte = ord($Locals_img{9});
                    $byte |= 0x80;
                    $byte &= 0xF8;
                    $byte |= (ord($this->BUF[0]{10}) & 0x07);
                    $Locals_img{9} = chr($byte);
                    $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
                }
            } else {
                $byte = ord($Locals_img{9});
                $byte |= 0x80;
                $byte &= 0xF8;
                $byte |= (ord($this->BUF[$i]{10}) & 0x07);
                $Locals_img{9} = chr($byte);
                $this->GIF .= ($Locals_ext . $Locals_img . $Locals_rgb . $Locals_tmp);
            }
        } else {
            $this->GIF .= ($Locals_ext . $Locals_img . $Locals_tmp);
        }
        $this->IMG = 1;
    }
    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GIFAddFooter...
    ::
     */
    function GIFAddFooter() {
        $this->GIF .= ";";
    }
    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GIFBlockCompare...
    ::
     */
    function GIFBlockCompare($GlobalBlock, $LocalBlock, $Len) {

        for ($i = 0; $i < $Len; $i++) {
            if (
                $GlobalBlock{3 * $i + 0} != $LocalBlock{3 * $i + 0} ||
                $GlobalBlock{3 * $i + 1} != $LocalBlock{3 * $i + 1} ||
                $GlobalBlock{3 * $i + 2} != $LocalBlock{3 * $i + 2}
            ) {
                return (0);
            }
        }

        return (1);
    }
    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GIFWord...
    ::
     */
    function GIFWord($int) {

        return (chr($int & 0xFF) . chr(($int >> 8) & 0xFF));
    }
    /*
    :::::::::::::::::::::::::::::::::::::::::::::::::::
    ::
    ::        GetAnimation...
    ::
     */
    function GetAnimation() {
        return ($this->GIF);
    }
}

class GifFrameExtractor {
    // Properties
    // ===================================================================================

    /**
     * @var resource
     */
    private $gif;

    /**
     * @var array
     */
    private $frames;

    /**
     * @var array
     */
    private $frameDurations;

    /**
     * @var array
     */
    private $frameImages;

    /**
     * @var array
     */
    private $framePositions;

    /**
     * @var array
     */
    private $frameDimensions;

    /**
     * @var integer
     *
     * (old: $this->index)
     */
    private $frameNumber;

    /**
     * @var array
     *
     * (old: $this->imagedata)
     */
    private $frameSources;

    /**
     * @var array
     *
     * (old: $this->fileHeader)
     */
    private $fileHeader;

    /**
     * @var integer The reader pointer in the file source
     *
     * (old: $this->pointer)
     */
    private $pointer;

    /**
     * @var integer
     */
    private $gifMaxWidth;

    /**
     * @var integer
     */
    private $gifMaxHeight;

    /**
     * @var integer
     */
    private $totalDuration;

    /**
     * @var integer
     */
    private $handle;

    /**
     * @var array
     *
     * (old: globaldata)
     */
    private $globaldata;

    /**
     * @var array
     *
     * (old: orgvars)
     */
    private $orgvars;

    // Methods
    // ===================================================================================

    /**
     * Extract frames of a GIF
     *
     * @param string $filename GIF filename path
     * @param boolean $originalFrames Get original frames (with transparent background)
     *
     * @return array
     */
    public function extract($filename, $originalFrames = false) {
        if (!self::isAnimatedGif($filename)) {
            throw new \Exception('The GIF image you are trying to explode is not animated !');
        }

        $this->reset();
        $this->parseFramesInfo($filename);
        $prevImg = null;

        for ($i = 0; $i < count($this->frameSources); $i++) {

            $this->frames[$i] = array();
            $this->frameDurations[$i] = $this->frames[$i]['duration'] = $this->frameSources[$i]['delay_time'];

            $img = imagecreatefromstring($this->fileHeader["gifheader"] . $this->frameSources[$i]["graphicsextension"] . $this->frameSources[$i]["imagedata"] . chr(0x3b));

            if (!$originalFrames) {

                if ($i > 0) {

                    $prevImg = $this->frames[$i - 1]['image'];

                } else {

                    $prevImg = $img;
                }

                $sprite = imagecreate($this->gifMaxWidth, $this->gifMaxHeight);
                imagesavealpha($sprite, true);

                $transparent = imagecolortransparent($prevImg);

                if ($transparent > -1 && imagecolorstotal($prevImg) > $transparent) {

                    $actualTrans = imagecolorsforindex($prevImg, $transparent);
                    imagecolortransparent($sprite, imagecolorallocate($sprite, $actualTrans['red'], $actualTrans['green'], $actualTrans['blue']));
                }

                if ((int) $this->frameSources[$i]['disposal_method'] == 1 && $i > 0) {

                    imagecopy($sprite, $prevImg, 0, 0, 0, 0, $this->gifMaxWidth, $this->gifMaxHeight);
                }

                imagecopyresampled($sprite, $img, $this->frameSources[$i]["offset_left"], $this->frameSources[$i]["offset_top"], 0, 0, $this->gifMaxWidth, $this->gifMaxHeight, $this->gifMaxWidth, $this->gifMaxHeight);
                $img = $sprite;
            }

            $this->frameImages[$i] = $this->frames[$i]['image'] = $img;
        }

        return $this->frames;
    }

    /**
     * Check if a GIF file at a path is animated or not
     *
     * @param string $filename GIF path
     */
    public static function isAnimatedGif($filename) {
        if (!($fh = @fopen($filename, 'rb'))) {
            return false;
        }

        $count = 0;
        while (!feof($fh) && $count < 2) {

            $chunk = fread($fh, 1024 * 100); //read 100kb at a time
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
        }

        fclose($fh);
        return $count > 1;
    }

    // Internals
    // ===================================================================================

    /**
     * Parse the frame informations contained in the GIF file
     *
     * @param string $filename GIF filename path
     */
    private function parseFramesInfo($filename) {
        $this->openFile($filename);
        $this->parseGifHeader();
        $this->parseGraphicsExtension(0);
        $this->getApplicationData();
        $this->getApplicationData();
        $this->getFrameString(0);
        $this->parseGraphicsExtension(1);
        $this->getCommentData();
        $this->getApplicationData();
        $this->getFrameString(1);

        while (!$this->checkByte(0x3b) && !$this->checkEOF()) {

            $this->getCommentData(1);
            $this->parseGraphicsExtension(2);
            $this->getFrameString(2);
            $this->getApplicationData();
        }
    }

    /**
     * Parse the gif header (old: get_gif_header)
     */
    private function parseGifHeader() {
        $this->pointerForward(10);

        if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) {

            $this->pointerForward(2);
            $this->pointerForward(pow(2, $this->readBits($mybyte, 5, 3) + 1) * 3);

        } else {

            $this->pointerForward(2);
        }
        $this->fileHeader["gifheader"] = $this->dataPart(0, $this->pointer);

        // Decoding
        $this->orgvars["gifheader"] = $this->fileHeader["gifheader"];
        $this->orgvars["background_color"] = $this->orgvars["gifheader"][11];
    }

    /**
     * Parse the application data of the frames (old: get_application_data)
     */
    private function getApplicationData() {
        $startdata = $this->readByte(2);

        if ($startdata == chr(0x21) . chr(0xff)) {

            $start = $this->pointer - 2;
            $this->pointerForward($this->readByteInt());
            $this->readDataStream($this->readByteInt());
            $this->fileHeader["applicationdata"] = $this->dataPart($start, $this->pointer - $start);

        } else {

            $this->pointerRewind(2);
        }
    }

    /**
     * Parse the comment data of the frames (old: get_comment_data)
     */
    private function getCommentData() {
        $startdata = $this->readByte(2);

        if ($startdata == chr(0x21) . chr(0xfe)) {

            $start = $this->pointer - 2;
            $this->readDataStream($this->readByteInt());
            $this->fileHeader["commentdata"] = $this->dataPart($start, $this->pointer - $start);

        } else {

            $this->pointerRewind(2);
        }
    }

    /**
     * Parse the graphic extension of the frames (old: get_graphics_extension)
     *
     * @param integer $type
     */
    private function parseGraphicsExtension($type) {
        $startdata = $this->readByte(2);

        if ($startdata == chr(0x21) . chr(0xf9)) {

            $start = $this->pointer - 2;
            $this->pointerForward($this->readByteInt());
            $this->pointerForward(1);

            if ($type == 2) {

                $this->frameSources[$this->frameNumber]["graphicsextension"] = $this->dataPart($start, $this->pointer - $start);

            } elseif ($type == 1) {

                $this->orgvars["hasgx_type_1"] = 1;
                $this->globaldata["graphicsextension"] = $this->dataPart($start, $this->pointer - $start);

            } elseif ($type == 0) {

                $this->orgvars["hasgx_type_0"] = 1;
                $this->globaldata["graphicsextension_0"] = $this->dataPart($start, $this->pointer - $start);
            }

        } else {

            $this->pointerRewind(2);
        }
    }

    /**
     * Get the full frame string block (old: get_image_block)
     *
     * @param integer $type
     */
    private function getFrameString($type) {
        if ($this->checkByte(0x2c)) {

            $start = $this->pointer;
            $this->pointerForward(9);

            if ($this->readBits(($mybyte = $this->readByteInt()), 0, 1) == 1) {

                $this->pointerForward(pow(2, $this->readBits($mybyte, 5, 3) + 1) * 3);
            }

            $this->pointerForward(1);
            $this->readDataStream($this->readByteInt());
            $this->frameSources[$this->frameNumber]["imagedata"] = $this->dataPart($start, $this->pointer - $start);
            if ($type == 0) {

                $this->orgvars["hasgx_type_0"] = 0;

                if (isset($this->globaldata["graphicsextension_0"])) {

                    $this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"];

                } else {

                    $this->frameSources[$this->frameNumber]["graphicsextension"] = null;
                }

                unset($this->globaldata["graphicsextension_0"]);

            } elseif ($type == 1) {

                if (isset($this->orgvars["hasgx_type_1"]) && $this->orgvars["hasgx_type_1"] == 1) {

                    $this->orgvars["hasgx_type_1"] = 0;
                    $this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension"];
                    unset($this->globaldata["graphicsextension"]);

                } else {

                    $this->orgvars["hasgx_type_0"] = 0;
                    $this->frameSources[$this->frameNumber]["graphicsextension"] = $this->globaldata["graphicsextension_0"];
                    unset($this->globaldata["graphicsextension_0"]);
                }
            }
            $this->parseFrameData();
            $this->frameNumber++;
        }
    }

    /**
     * Parse frame data string into an array (old: parse_image_data)
     */
    private function parseFrameData() {
        $this->frameSources[$this->frameNumber]["disposal_method"] = $this->getImageDataBit("ext", 3, 3, 3);
        $this->frameSources[$this->frameNumber]["user_input_flag"] = $this->getImageDataBit("ext", 3, 6, 1);
        $this->frameSources[$this->frameNumber]["transparent_color_flag"] = $this->getImageDataBit("ext", 3, 7, 1);
        $this->frameSources[$this->frameNumber]["delay_time"] = $this->dualByteVal($this->getImageDataByte("ext", 4, 2));
        $this->totalDuration += (int) $this->frameSources[$this->frameNumber]["delay_time"];
        $this->frameSources[$this->frameNumber]["transparent_color_index"] = ord($this->getImageDataByte("ext", 6, 1));
        $this->frameSources[$this->frameNumber]["offset_left"] = $this->dualByteVal($this->getImageDataByte("dat", 1, 2));
        $this->frameSources[$this->frameNumber]["offset_top"] = $this->dualByteVal($this->getImageDataByte("dat", 3, 2));
        $this->frameSources[$this->frameNumber]["width"] = $this->dualByteVal($this->getImageDataByte("dat", 5, 2));
        $this->frameSources[$this->frameNumber]["height"] = $this->dualByteVal($this->getImageDataByte("dat", 7, 2));
        $this->frameSources[$this->frameNumber]["local_color_table_flag"] = $this->getImageDataBit("dat", 9, 0, 1);
        $this->frameSources[$this->frameNumber]["interlace_flag"] = $this->getImageDataBit("dat", 9, 1, 1);
        $this->frameSources[$this->frameNumber]["sort_flag"] = $this->getImageDataBit("dat", 9, 2, 1);
        $this->frameSources[$this->frameNumber]["color_table_size"] = pow(2, $this->getImageDataBit("dat", 9, 5, 3) + 1) * 3;
        $this->frameSources[$this->frameNumber]["color_table"] = substr($this->frameSources[$this->frameNumber]["imagedata"], 10, $this->frameSources[$this->frameNumber]["color_table_size"]);
        $this->frameSources[$this->frameNumber]["lzw_code_size"] = ord($this->getImageDataByte("dat", 10, 1));

        $this->framePositions[$this->frameNumber] = array(
            'x' => $this->frameSources[$this->frameNumber]["offset_left"],
            'y' => $this->frameSources[$this->frameNumber]["offset_top"],
        );

        $this->frameDimensions[$this->frameNumber] = array(
            'width' => $this->frameSources[$this->frameNumber]["width"],
            'height' => $this->frameSources[$this->frameNumber]["height"],
        );

        // Decoding
        $this->orgvars[$this->frameNumber]["transparent_color_flag"] = $this->frameSources[$this->frameNumber]["transparent_color_flag"];
        $this->orgvars[$this->frameNumber]["transparent_color_index"] = $this->frameSources[$this->frameNumber]["transparent_color_index"];
        $this->orgvars[$this->frameNumber]["delay_time"] = $this->frameSources[$this->frameNumber]["delay_time"];
        $this->orgvars[$this->frameNumber]["disposal_method"] = $this->frameSources[$this->frameNumber]["disposal_method"];
        $this->orgvars[$this->frameNumber]["offset_left"] = $this->frameSources[$this->frameNumber]["offset_left"];
        $this->orgvars[$this->frameNumber]["offset_top"] = $this->frameSources[$this->frameNumber]["offset_top"];

        // Updating the max width
        if ($this->gifMaxWidth < $this->frameSources[$this->frameNumber]["width"]) {

            $this->gifMaxWidth = $this->frameSources[$this->frameNumber]["width"];
        }

        // Updating the max height
        if ($this->gifMaxHeight < $this->frameSources[$this->frameNumber]["height"]) {

            $this->gifMaxHeight = $this->frameSources[$this->frameNumber]["height"];
        }
    }

    /**
     * Get the image data byte (old: get_imagedata_byte)
     *
     * @param string $type
     * @param integer $start
     * @param integer $length
     *
     * @return string
     */
    private function getImageDataByte($type, $start, $length) {
        if ($type == "ext") {

            return substr($this->frameSources[$this->frameNumber]["graphicsextension"], $start, $length);
        }

        // "dat"
        return substr($this->frameSources[$this->frameNumber]["imagedata"], $start, $length);
    }

    /**
     * Get the image data bit (old: get_imagedata_bit)
     *
     * @param string $type
     * @param integer $byteIndex
     * @param integer $bitStart
     * @param integer $bitLength
     *
     * @return number
     */
    private function getImageDataBit($type, $byteIndex, $bitStart, $bitLength) {
        if ($type == "ext") {

            return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["graphicsextension"], $byteIndex, 1)), $bitStart, $bitLength);
        }

        // "dat"
        return $this->readBits(ord(substr($this->frameSources[$this->frameNumber]["imagedata"], $byteIndex, 1)), $bitStart, $bitLength);
    }

    /**
     * Return the value of 2 ASCII chars (old: dualbyteval)
     *
     * @param string $s
     *
     * @return integer
     */
    private function dualByteVal($s) {
        $i = ord($s[1]) * 256 + ord($s[0]);

        return $i;
    }

    /**
     * Read the data stream (old: read_data_stream)
     *
     * @param integer $firstLength
     */
    private function readDataStream($firstLength) {
        $this->pointerForward($firstLength);
        $length = $this->readByteInt();

        if ($length != 0) {

            while ($length != 0) {

                $this->pointerForward($length);
                $length = $this->readByteInt();
            }
        }
    }

    /**
     * Open the gif file (old: loadfile)
     *
     * @param string $filename
     */
    private function openFile($filename) {
        $this->handle = fopen($filename, "rb");
        $this->pointer = 0;

        $imageSize = getimagesize($filename);
        $this->gifWidth = $imageSize[0];
        $this->gifHeight = $imageSize[1];
    }

    /**
     * Close the read gif file (old: closefile)
     */
    private function closeFile() {
        fclose($this->handle);
        $this->handle = 0;
    }

    /**
     * Read the file from the beginning to $byteCount in binary (old: readbyte)
     *
     * @param integer $byteCount
     *
     * @return string
     */
    private function readByte($byteCount) {
        $data = fread($this->handle, $byteCount);
        $this->pointer += $byteCount;

        return $data;
    }

    /**
     * Read a byte and return ASCII value (old: readbyte_int)
     *
     * @return integer
     */
    private function readByteInt() {
        $data = fread($this->handle, 1);
        $this->pointer++;

        return ord($data);
    }

    /**
     * Convert a $byte to decimal (old: readbits)
     *
     * @param string $byte
     * @param integer $start
     * @param integer $length
     *
     * @return number
     */
    private function readBits($byte, $start, $length) {
        $bin = str_pad(decbin($byte), 8, "0", STR_PAD_LEFT);
        $data = substr($bin, $start, $length);

        return bindec($data);
    }

    /**
     * Rewind the file pointer reader (old: p_rewind)
     *
     * @param integer $length
     */
    private function pointerRewind($length) {
        $this->pointer -= $length;
        fseek($this->handle, $this->pointer);
    }

    /**
     * Forward the file pointer reader (old: p_forward)
     *
     * @param integer $length
     */
    private function pointerForward($length) {
        $this->pointer += $length;
        fseek($this->handle, $this->pointer);
    }

    /**
     * Get a section of the data from $start to $start + $length (old: datapart)
     *
     * @param integer $start
     * @param integer $length
     *
     * @return string
     */
    private function dataPart($start, $length) {
        fseek($this->handle, $start);
        $data = fread($this->handle, $length);
        fseek($this->handle, $this->pointer);

        return $data;
    }

    /**
     * Check if a character if a byte (old: checkbyte)
     *
     * @param integer $byte
     *
     * @return boolean
     */
    private function checkByte($byte) {
        if (fgetc($this->handle) == chr($byte)) {

            fseek($this->handle, $this->pointer);
            return true;
        }

        fseek($this->handle, $this->pointer);
        return false;
    }

    /**
     * Check the end of the file (old: checkEOF)
     *
     * @return boolean
     */
    private function checkEOF() {
        if (fgetc($this->handle) === false) {

            return true;
        }

        fseek($this->handle, $this->pointer);

        return false;
    }

    /**
     * Reset and clear this current object
     */
    private function reset() {
        $this->gif = null;
        $this->totalDuration = $this->gifMaxHeight = $this->gifMaxWidth = $this->handle = $this->pointer = $this->frameNumber = 0;
        $this->frameDimensions = $this->framePositions = $this->frameImages = $this->frameDurations = $this->globaldata = $this->orgvars = $this->frames = $this->fileHeader = $this->frameSources = array();
    }

    // Getter / Setter
    // ===================================================================================

    /**
     * Get the total of all added frame duration
     *
     * @return integer
     */
    public function getTotalDuration() {
        return $this->totalDuration;
    }

    /**
     * Get the number of extracted frames
     *
     * @return integer
     */
    public function getFrameNumber() {
        return $this->frameNumber;
    }

    /**
     * Get the extracted frames (images and durations)
     *
     * @return array
     */
    public function getFrames() {
        return $this->frames;
    }

    /**
     * Get the extracted frame positions
     *
     * @return array
     */
    public function getFramePositions() {
        return $this->framePositions;
    }

    /**
     * Get the extracted frame dimensions
     *
     * @return array
     */
    public function getFrameDimensions() {
        return $this->frameDimensions;
    }

    /**
     * Get the extracted frame images
     *
     * @return array
     */
    public function getFrameImages() {
        return $this->frameImages;
    }

    /**
     * Get the extracted frame durations
     *
     * @return array
     */
    public function getFrameDurations() {
        return $this->frameDurations;
    }
}

